/**
 * CS543 HW4 Joseph Meyer
 */
package edu.src.paging;

import edu.src.address.Address;
import edu.src.address.ReadWriteBit;
import edu.src.hardware.BackingStore;
import edu.src.hardware.PhysicalMemory;
import edu.src.logger.Logger;

/**
 * Page size of 256.
 * Holds the framenumber and invalid/valid bit of a page table entry
 * @author meyer
 *
 */
public class Page {
	/** Timestampe records the last populated time of this page. 
	 * Used for LRU replacement strategy*/
	private long timestamp;
	private Logger logger;
	private Integer frameNumber;
	private PageBit bit;
	private int physicalMemoryAddress;
	private ReadWriteBit rwbit;
	
	/**
	 * Constructor
	 * @param frameNumber - reference to the frame / page
	 */
	public Page(Integer frameNumber) {
		this.timestamp = 0;
		this.frameNumber = null;
		bit = PageBit.INVALID;
		logger = new Logger(this.getClass());
		rwbit = ReadWriteBit.NONE;
	}
	
	/**
	 * Constructor
	 * defaults frameNumber to null
	 * should be populated once a value is loaded from memory.
	 */
	public Page() {
		this(null);
	}

	/**
	 * @return the frameNumber
	 */
	public Integer getFrameNumber() {
		return frameNumber;
	}

	/**
	 * safe copy of Integer
	 * 
	 * @param frameNumber the frameNumber to set
	 */
	public void setFrameNumber(Integer frameNumber) {
		if (frameNumber != null) {
			this.frameNumber = new Integer(frameNumber.intValue());
		}
	}

	/**
	 * @return the bit
	 */
	public PageBit getBit() {
		return bit;
	}

	/**
	 * @param bit the bit to set
	 */
	public void setBit(PageBit bit) {
		this.bit = bit;
	}
	
	
	/**
	 * @return the timestamp - Epoch time
	 */
	public long getTimestamp() {
		return timestamp;
	}

	/**
	 * @param timestamp the timestamp to set in millis
	 */
	public void setTimestamp(long timestamp) {
		this.timestamp = timestamp;
	}

	/**
	 * This is called from a request to load an 
	 * item into physical memory.
	 * This will read the entry from the disk (backingstore in this case),
	 * load the item into physical memory and finally record the loaded
	 * value in the paging table
	 * @param Address - used to create the Page
	 */
	public void loadIntoPage(Address address) {
		setRwbit(address.getDirtyBit());
		loadIntoPage(address.getPageNumber(), address.getOffset());
	}
	
	/**
	 * <b>do not expose this - <code>Page</code>'s must be constructed with an <code>Address</code></b>
	 * <p>
	 * This is called from a request to load an 
	 * item into physical memory.
	 * This will read the entry from the disk (backingstore in this case),
	 * load the item into physical memory and finally record the loaded
	 * value in the paging table
	 */
	private void loadIntoPage(int pageNum, int offset) {
		try {
			PhysicalMemory pm = PhysicalMemory.getInstance();
			BackingStore bs = BackingStore.getInstance();
			byte b = bs.getByteAt(pageNum, offset);
			
			//there is nothing at this spot in memory. Load the Page
			int physicalAddress = pm.addByteToPhysicalMemory(b, offset);//, physicalAddress);
			setBit(PageBit.VALID);
			setFrameNumber(pageNum);
			setPhysicalMemoryAddress(physicalAddress);
			timestamp = System.currentTimeMillis();

		} catch (Exception e) {
			logger.error("Failed to load item from the backing store to physical memory and the page table", e);
		}
	}
	
	/**
	 * @return the physicalMemoryAddress
	 */
	public int getPhysicalMemoryAddress() {
		return physicalMemoryAddress;
	}

	/**
	 * @param physicalMemoryAddress the physicalMemoryAddress to set
	 */
	public void setPhysicalMemoryAddress(int physicalMemoryAddress) {
		this.physicalMemoryAddress = physicalMemoryAddress;
	}

	/**
	 * @return the rwbit
	 */
	public ReadWriteBit getRwbit() {
		return rwbit;
	}

	/**
	 * @param rwbit the rwbit to set
	 */
	public void setRwbit(ReadWriteBit rwbit) {
		this.rwbit = rwbit;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "Page [logger=" + logger + ", frameNumber=" + frameNumber
				+ ", bit=" + bit + "]";
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((bit == null) ? 0 : bit.hashCode());
		result = prime * result
				+ ((frameNumber == null) ? 0 : frameNumber.hashCode());
		result = prime * result + (int) (timestamp ^ (timestamp >>> 32));
		return result;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Page other = (Page) obj;
		if (bit != other.bit)
			return false;
		if (frameNumber == null) {
			if (other.frameNumber != null)
				return false;
		} else if (!frameNumber.equals(other.frameNumber))
			return false;
		if (timestamp != other.timestamp)
			return false;
		return true;
	}
	
}
